package com.freewater.library.lending.reservedsheet.eventhandler;

import com.freewater.library.catalogue.model.Book;
import com.freewater.library.catalogue.model.BookRepository;
import com.freewater.library.common.domain.DomainException;
import com.freewater.library.lending.book.model.BookInstance;
import com.freewater.library.lending.book.model.BookInstanceRepository;
import com.freewater.library.lending.book.model.event.BookInstanceCheckedOutEvent;
import com.freewater.library.lending.book.model.event.BookInstanceReserveCanceledEvent;
import com.freewater.library.lending.book.model.event.BookInstanceReservedEvent;
import com.freewater.library.lending.reservedsheet.infrastructure.dao.BookReservedRecordDao;
import com.freewater.library.lending.reservedsheet.model.BookReservedRecord;
import com.freewater.library.lending.reader.model.Reader;
import com.freewater.library.lending.reader.model.ReaderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Optional;

@Slf4j
@Component
@RequiredArgsConstructor
public class ReservedRecordConsumer {

    private final BookReservedRecordDao bookReservedRecordDao;
    private final BookRepository bookRepository;
    private final BookInstanceRepository bookInstanceRepository;
    private final ReaderRepository readerRepository;

    @Transactional
    @EventListener(BookInstanceReservedEvent.class)
    public void onBookReserved(BookInstanceReservedEvent event) {
        String qrCode = event.getQrCode();
        String isbn = event.getIsbn();
        Long readerId = event.getReaderId();
        LocalDateTime reservedTime = event.getOccurAt();
        Reader reader = this.requireReader(readerId);
        Book book = this.requireBook(isbn);
        BookInstance bookInstance = this.requireBookInstance(qrCode);
        // 读者最近一次预定此书的记录
        BookReservedRecord.Unique unique = new BookReservedRecord.Unique(qrCode, readerId, reservedTime);
        boolean exists = this.bookReservedRecordDao.existsByUnique(unique);
        BookReservedRecord reservedRecord =
                new BookReservedRecord(book, bookInstance, reader, reservedTime);
        // 没有记录或者不是相同的记录则新增
        if (!exists) {
            this.bookReservedRecordDao.save(reservedRecord);
        } else {
            log.warn("重复预定事件: {}", event);
        }
    }

    @Transactional
    @EventListener(BookInstanceCheckedOutEvent.class)
    public void onBookCheckedOut(BookInstanceCheckedOutEvent event) {
        LocalDateTime checkOutTime = event.getOccurAt();
        BookReservedRecord.Unique unique = new BookReservedRecord.Unique(event.getQrCode(),
                event.getReaderId(), event.getReservedAt());
        Optional<BookReservedRecord> recordOpt = bookReservedRecordDao.findByUnique(unique);
        if (recordOpt.isPresent()) {
            BookReservedRecord reservedRecord = recordOpt.get();
            reservedRecord.checkedOut(checkOutTime);
            this.bookReservedRecordDao.save(reservedRecord);
        } else {
            log.warn("没有找到预定记录: {}", event);
        }
    }

    @Transactional
    @EventListener(BookInstanceReserveCanceledEvent.class)
    public void onBookReserveCanceled(BookInstanceReserveCanceledEvent event) {
        LocalDateTime cancelTime = event.getOccurAt();
        String cancelReason = event.getReason();
        BookReservedRecord.Unique unique = new BookReservedRecord.Unique(event.getQrCode(),
                event.getReaderId(), event.getReservedAt());
        Optional<BookReservedRecord> recordOpt = bookReservedRecordDao.findByUnique(unique);
        if (recordOpt.isPresent()) {
            BookReservedRecord reservedRecord = recordOpt.get();
            reservedRecord.canceled(cancelTime, cancelReason);
            this.bookReservedRecordDao.save(reservedRecord);
        } else {
            log.warn("没有找到预定记录: {}", event);
        }
    }

    private Book requireBook(String isbn) {
        Optional<Book> bookOpt = this.bookRepository.findById(isbn);
        return bookOpt.orElseThrow(() -> new DomainException("无效的书"));
    }

    private BookInstance requireBookInstance(String qrCode) {
        Optional<BookInstance> bookOpt = this.bookInstanceRepository.findById(qrCode);
        return bookOpt.orElseThrow(() -> new DomainException("无效的书"));
    }

    private Reader requireReader(Long readerId) {
        Optional<Reader> readerOpt = this.readerRepository.findById(readerId);
        return readerOpt.orElseThrow(() -> new DomainException("无效的读者"));
    }
}
